package org.freedoit.server.service.task;

import java.io.IOException;
import java.lang.reflect.Type;
import java.security.interfaces.RSAPrivateKey;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.freedoit.common.data.FreeDoitRequest;
import org.freedoit.common.data.FreeDoitResponse;
import org.freedoit.common.util.FreeDoitDataUtils;
import org.freedoit.common.util.SharedConstants;
import org.freedoit.server.om.User;
import org.freedoit.shared.util.Constants;

import com.google.gson.Gson;

public abstract class AbstractTaskServlet<T extends FreeDoitRequest, R extends FreeDoitResponse> extends HttpServlet {

	/**
	 * 
	 */
	private static final long serialVersionUID = -5003094136199773219L;

	/**
	 * 
	 */
	
	public void doPost(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {
		
		// Build Context first
		ServiceContext <T, R> context = new ServiceContext <T, R>();
		R responseObject = createResponseObject();
		context.setResponseObject(responseObject);
		 
		// Check Request
		Gson gson = FreeDoitDataUtils.createStandardJson();
		RSAPrivateKey priKey = getCurrentPrivateKey(request);
		if (checkRequest(request, context, gson, priKey)) {
			processRequest(context, System.currentTimeMillis());
		}
		
		// write back to response by encrypting
		String msg = gson.toJson(responseObject,  new com.google.gson.reflect.TypeToken<R>() {  
						}.getType());
		response.getWriter().write(FreeDoitDataUtils.encryptByAES(msg, context.getKey()));
	}
	
	protected abstract R createResponseObject();
	
	protected abstract Type getRequestType();
	
	private boolean checkRequest(HttpServletRequest request, ServiceContext <T, R> context, Gson gson, RSAPrivateKey priKey) {
		String messageEncrypted = request.getParameter(SharedConstants.DATA_KEY);
		if (messageEncrypted == null) {
			System.err.println("Cannot find the param " + SharedConstants.DATA_KEY);
			context.getResponseObject().setCode(FreeDoitResponse.CODE_INVALID_REQUEST);
			return false;
		}

		Long currentUserId = getCurrentUserId(request);
		Long currentClientId = getCurrentClientId(request);
		if (currentUserId == null || currentClientId == null || priKey == null) {
			context.getResponseObject().setCode(FreeDoitResponse.CODE_lOGIN_REQUIRED);
			return false;
		}

		context.setCurrentUserId(currentUserId);
		context.setCurrentClientId(currentClientId);
		
		// Decrypt the key by RSA first
		// This key is used to decrypt the message by AES
		String encryptedKey = request.getParameter(SharedConstants.KEY);
		if (encryptedKey == null) {
			System.err.println("Cannot find the param " + SharedConstants.KEY);
			context.getResponseObject().setCode(FreeDoitResponse.CODE_INVALID_REQUEST);
			return false;
		}
		
		String decryptedKey = FreeDoitDataUtils.decryptByRSA(priKey, encryptedKey);
		if (decryptedKey == null) {
			System.err.println("Cannot parse the incoming key");
			context.getResponseObject().setCode(FreeDoitResponse.CODE_INVALID_DATA);
			return false;
		}
		
		context.setKey(decryptedKey);
		
		// Decrypt message
		String message = FreeDoitDataUtils.decryptByAES(messageEncrypted, decryptedKey);
		if (message == null) {
			System.err.println("Cannot parse the incoming message");
			context.getResponseObject().setCode(FreeDoitResponse.CODE_INVALID_DATA);
			return false;
		}
		System.out.println("Message: " + message);

		T requestObject = null;
		try {
			requestObject = gson.fromJson(message, getRequestType());
		} catch (Exception e) {
			e.printStackTrace();
		}
			
		if (requestObject == null) {
			System.err.println("Cannot parse the incoming message");
			context.getResponseObject().setCode(FreeDoitResponse.CODE_INVALID_REQUEST);
			return false;
		} else {
			context.setRequestObject(requestObject);
			return true;
		}
		
	}
	
	private Long getCurrentUserId(HttpServletRequest request)
	{
		User currentUser = (User)(request.getSession().getAttribute(Constants.DOIT_USER_KEY));
		if (currentUser == null) {
			return null;
		} else {
			return currentUser.getUserId();
		}
		
	}
	
	private Long getCurrentClientId(HttpServletRequest request)
	{
		return (Long)(request.getSession().getAttribute(Constants.DOIT_CLIENT_KEY));
		
	}
	
	private RSAPrivateKey getCurrentPrivateKey(HttpServletRequest request)
	{
		return (RSAPrivateKey)(request.getSession().getAttribute(Constants.DOIT_PRI_KEY));
		
	}
	
	protected abstract void processRequest(ServiceContext <T, R> context, Long timestamp);
}
